﻿using System;
using System.Collections.Generic;
using QFramework;
using UnityEngine;

namespace Gp.Scripts.Core
{
    public delegate void ScheduleDelegate(float interval);

    public class ScheduleUnRegister : IUnRegister
    {
        private readonly Action _action;

        public ScheduleUnRegister(Action action)
        {
            _action = action;
        }

        public void UnRegister()
        {
            _action?.Invoke();
        }
    }

    [AutoLoad]
    public class ScheduleManager : BaseGameManager
    {
        private List<ScheduleSequence> _eSequences;
        private List<ScheduleSequence> _removingSequence;
        private List<DelayTask> _delayTasks;
        private List<DelayTask> _removingTasks;
        private int _uidCnt = 1;

        protected override void OnInit()
        {
            _eSequences       = new List<ScheduleSequence>();
            _removingSequence = new List<ScheduleSequence>();
            _delayTasks       = new List<DelayTask>();
            _removingTasks    = new List<DelayTask>();
        }


        public override void OnUpdate()
        {
            foreach (var task in _removingTasks)
            {
                _delayTasks.Remove(task);
            }

            _removingTasks.Clear();

            foreach (var sequence in _removingSequence)
            {
                _eSequences.Remove(sequence);
            }

            // 更新延时任务
            _removingSequence.Clear();

            // 倒序遍历，即使在更新时发生了新增或移除操作，当前序列都不会影响
            for (int i = _eSequences.Count - 1; i >= 0; i--)
            {
                var   sequence  = _eSequences[i];
                float deltaTime = Time.time - sequence.LastExecuteTime;
                if (deltaTime < sequence.Interval) continue;
                
                CommonUtils.SafeCall(() => sequence.Func.Invoke(deltaTime), nameof(ScheduleManager),
                    "Error occurred while updating schedule sequences");
                sequence.LastExecuteTime = Time.time;
            }

            for (int i = _delayTasks.Count - 1; i >= 0; i--)
            {
                var task = _delayTasks[i];
                if (!task.CanInvoke()) continue;
                
                CommonUtils.SafeCall(task.Callback, nameof(ScheduleManager), "Error occurred while scheduling delay task");
                _delayTasks.RemoveAt(i);
            }
        }

        public IUnRegister RegisterSchedule(float interval, ScheduleDelegate callback)
        {
            var sequence = new ScheduleSequence(GetUID()) {Func = callback, Interval = interval, LastExecuteTime = 0};
            _eSequences.Add(sequence);
            return new ScheduleUnRegister(() => { _removingSequence.Add(sequence); });
        }

        public IUnRegister DelayCall(float delayTime, Action callback)
        {
            var task = new TimeDelayTask(Time.time, delayTime, callback);
            this._delayTasks.Add(task);
            return new ScheduleUnRegister(() => _removingTasks.Add(task));
        }

        public IUnRegister DelayFrame(int frameCount, Action callback)
        {
            var task = new FrameDelayTask(Time.frameCount, Time.frameCount + frameCount, callback);
            this._delayTasks.Add(task);
            return new ScheduleUnRegister(() => _removingTasks.Add(task));
        }


        private int GetUID()
        {
            return _uidCnt++;
        }


        private class ScheduleSequence
        {
            public readonly int Uid;
            public ScheduleDelegate Func;
            public float Interval;
            public float LastExecuteTime;

            public ScheduleSequence(int uid)
            {
                Uid = uid;
            }
        }

        private abstract class DelayTask
        {
            public Action Callback;

            public DelayTask(Action callback)
            {
                Callback = callback;
            }

            public abstract bool CanInvoke();
        }

        private class TimeDelayTask : DelayTask
        {
            private readonly float _startTime;
            private readonly float _delayTime;

            public TimeDelayTask(float startTime, float delayTime, Action callback) : base(callback)
            {
                _startTime = startTime;
                _delayTime = delayTime;
            }

            public TimeDelayTask(Action callback) : base(callback) { }

            public void Update() { }

            public override bool CanInvoke()
            {
                return Time.time >= _delayTime;
            }
        }

        private class FrameDelayTask : DelayTask
        {
            public int StartFrame;
            public readonly int DelayFrame;

            public FrameDelayTask(int delayDelayFrame, int startFrame, Action callback) : base(callback)
            {
                DelayFrame = delayDelayFrame;
                StartFrame = startFrame;
            }

            public override bool CanInvoke()
            {
                return Time.frameCount > DelayFrame;
            }
        }
    }
}